Skip to content

Add HistoryEventSerializationBinder to constrain HistoryEvent deserialization#1343

Closed
AnatoliB wants to merge 5 commits intomainfrom
anatolib/codeql-fix-37181649
Closed

Add HistoryEventSerializationBinder to constrain HistoryEvent deserialization#1343
AnatoliB wants to merge 5 commits intomainfrom
anatolib/codeql-fix-37181649

Conversation

@AnatoliB
Copy link
Copy Markdown
Collaborator

@AnatoliB AnatoliB commented May 1, 2026

Copilot AI review requested due to automatic review settings May 1, 2026 01:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a restricted JSON serialization binder for Azure Table–backed orchestration history event deserialization in the ServiceBus backend to reduce unsafe $type-based deserialization exposure.

Changes:

  • Introduced HistoryEventSerializationBinder to allowlist deserializable types for history event payloads.
  • Updated Azure Table history entity deserialization to use the new binder.
  • Added MSTest coverage validating round-trip behavior and rejection of non-allowlisted $type values.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/DurableTask.ServiceBus/Tracking/HistoryEventSerializationBinder.cs New restrictive binder (extends PackageUpgradeSerializationBinder) that rejects non-allowlisted resolved types.
src/DurableTask.ServiceBus/Tracking/AzureTableOrchestrationHistoryEventEntity.cs Switches history event read settings to use the new restrictive binder.
Test/DurableTask.ServiceBus.Tests/HistoryEventSerializationBinderTests.cs Adds tests for round-trip tags and $type rejection/allow scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Test/DurableTask.ServiceBus.Tests/HistoryEventSerializationBinderTests.cs Outdated
@AnatoliB AnatoliB force-pushed the anatolib/codeql-fix-37181649 branch from 25d2db1 to b168f20 Compare May 1, 2026 01:43
Copilot AI review requested due to automatic review settings May 1, 2026 20:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// Stage 2: delegate to PackageUpgradeSerializationBinder for the legacy
// DurableTask.* -> DurableTask.Core.* rewrite and standard type resolution.
Type resolved = base.BindToType(assemblyName, typeName);
Comment on lines +82 to +92
static bool IsAllowed(Type type)
{
// Allow any type defined in DurableTask.Core (HistoryEvent subclasses, OrchestrationInstance,
// ParentInstance, FailureDetails, OrchestrationExecutionContext, etc.), plus
// Dictionary<string, string> to round-trip the IDictionary<string, string> Tags members
// declared on ExecutionStartedEvent / SubOrchestrationInstanceCreatedEvent /
// TaskScheduledEvent (Newtonsoft.Json emits a $type for these because the static
// declared type is an interface).
return type.Assembly == DurableTaskCoreAssembly
|| type == typeof(Dictionary<string, string>);
}
Comment on lines +25 to +33
[TestClass]
public class HistoryEventSerializationBinderTests
{
// Mirrors the production read settings on AzureTableOrchestrationHistoryEventEntity.
static readonly JsonSerializerSettings ReadJsonSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
SerializationBinder = new HistoryEventSerializationBinder()
};
AnatoliB added 3 commits May 1, 2026 13:46
- Reject null/empty assembly names in BindToType to avoid NRE from base PackageUpgradeSerializationBinder when $type lacks an assembly portion.

- Broaden assembly allowlist to host assemblies of common IDictionary<string,string> implementations (Dictionary, SortedDictionary, ConcurrentDictionary, ReadOnlyDictionary) so historical Tags persisted with non-Dictionary runtime types continue to deserialize after public APIs accept any IDictionary<string,string>.

- Replace exact-Dictionary<string,string> post-check with IDictionary<string,string>-shape check; gadget types in the same BCL assembly (e.g., FileInfo) are still rejected.

- Add tests: RejectsNullAssemblyName, RejectsNonBclDictionaryAssembly, AllowsSortedDictionaryStringStringForTags. Update RejectsNonAllowlistedNestedType to use Hashtable (BCL but non-IDictionary<string,string>) to exercise the post-resolution shape filter.
The ServiceBus test csproj lives at test/DurableTask.ServiceBus.Tests, but the file was committed under Test/ (capital T). On case-sensitive filesystems (Linux CI) the SDK glob rooted at the lowercase csproj folder would not pick this file up, meaning the new binder tests would not run in CI.
Copilot AI review requested due to automatic review settings May 1, 2026 21:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


private static readonly JsonSerializerSettings ReadJsonSettings = new JsonSerializerSettings
{
// CodeQL [SM02211] Constrained by HistoryEventSerializationBinder allowlist; only DurableTask.Core types and Dictionary<string,string> are accepted.
Comment on lines +106 to +114
// For all other allowlisted assemblies (the BCL dictionary hosts plus the legacy
// pre-v2 DTFx assemblies), only types assignable to IDictionary<string, string>
// are accepted. This narrows the resolved set so that gadget types living in the
// same allowlisted BCL assembly (e.g., FileInfo in System.Private.CoreLib) cannot
// pass the post-resolution check. Tags is the only IDictionary<string, string>
// member declared on a serialized HistoryEvent subtree, so any other concrete
// type is unreachable by the legitimate serializer in any case.
return typeof(IDictionary<string, string>).IsAssignableFrom(type);
}
@AnatoliB AnatoliB closed this May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants